/*jQuery Form to Form Wizard (Initial: Oct 1st, 2010)
* This notice must stay intact for usage
* Author: Dynamic Drive at http://www.dynamicdrive.com/
* Visit http://www.dynamicdrive.com/ for full source code
*/

//Oct 21st, 2010: Script updated to v1.1, which adds basic form validation functionality, triggered each time the user goes from one page to the next, or tries to submit the form.

//jQuery.noConflict()


function formtowizard(options){
    this.setting=jQuery.extend({persistsection:false, revealfx:['slide', 500], oninit:function(){}, onpagechangestart:function(){}}, options);
    this.currentsection=-1;
    this.init(this.setting);
}

formtowizard.prototype={

    createfieldsets:function($theform, arr){ //reserved function for future version (dynamically wraps form elements with a fieldset element)
        $theform.find('fieldset.sectionwrap').removeClass('sectionwrap'); //make sure no fieldsets carry 'sectionwrap' before proceeding
        var $startelement=$theform.find(':first-child'); //reference first element inside form
        for (var i=0; i<arr.length; i++){ //loop thru "break" elements
            var $fieldsetelements=$startelement.nextUntil('#'+arr[i].breakafter+', *[name='+arr[i].breakafter+']').andSelf(); //reference all elements from start element to break element (nextUntil() is jQuery 1.4 function)
            $fieldsetelements.add($fieldsetelements.next()).wrapAll('<fieldset class="sectionwrap" />'); //wrap these elements with fieldset element
            $startelement=$theform.find('fieldset.sectionwrap').eq(i).prepend('<legend class="legendStep">'+arr[i].legend+'</legend>').next(); //increment startelement to begin at the end of the just inserted fieldset element
        }
    },

    loadsection:function(rawi, bypasshooks){
        var thiswizard=this;
      //doload Boolean checks to see whether to load next section (true if bypasshooks param is true or onpagechangestart() event handler doesn't return false)
        var doload=bypasshooks || this.setting.onpagechangestart(jQuery, this.currentsection, this.sections.$sections.eq(this.currentsection));
        doload=(doload===false)? false : true; //unless doload is explicitly false, set to true
        if (!bypasshooks && this.setting.validate){
            var outcome=this.validate(this.currentsection);
            if (outcome===false) doload=false;
        }
        var i=(rawi=="prev")? this.currentsection-1 : (rawi=="next")? this.currentsection+1 : parseInt(rawi); //get index of next section to show
        i=(i<0)? this.sections.count-1 : (i>this.sections.count-1)? 0 : i; //make sure i doesn't exceed min/max limit
        if (i<this.sections.count && doload){ //if next section to show isn't the same as the current section shown
            this.$thesteps.eq(this.currentsection).addClass('disabledstep').end().eq(i).removeClass('disabledstep'); //dull current "step" text then highlight next "step" text
            if (this.setting.revealfx[0]=="slide"){
                this.sections.$sections.css("visibility", "visible");
                this.sections.$outerwrapper.stop().animate({height: this.sections.$sections.eq(i).outerHeight()}, this.setting.revealfx[1]); //animate fieldset wrapper's height to accomodate next section's height
                this.sections.$innerwrapper.stop().animate({left:-i*this.maxfieldsetwidth}, this.setting.revealfx[1], function(){ //slide next section into view
                    thiswizard.sections.$sections.each(function(thissec){
                        if (thissec!=i) thiswizard.sections.$sections.eq(thissec).css("visibility", "hidden"); //hide fieldset sections currently not in veiw, so tabbing doesn't go to elements within them (and mess up layout)
                    });
                });
            } else if (this.setting.revealfx[0]=="fade"){ //if fx is "fade"
                this.sections.$sections.eq(this.currentsection).hide().end().eq(i).fadeIn(this.setting.revealfx[1], function(){
                    if (document.all && this.style && this.style.removeAttribute) this.style.removeAttribute('filter'); //fix IE clearType problem
                });
            } else {
                this.sections.$sections.eq(this.currentsection).hide().end().eq(i).show();
            }
            this.paginatediv.$status.text("Page "+(i+1)+" of "+this.sections.count); //update current page status text
            this.paginatediv.$navlinks.css('visibility', 'visible');
            if (i===0) { //hide "prev" link
                this.paginatediv.$navlinks.eq(0).css('visibility', 'hidden');
            } else if (i==this.sections.count-1){ //hide "next" link
                this.paginatediv.$navlinks.eq(1).css('visibility', 'hidden');
            }
            if (this.setting.persistsection) formtowizard.routines.setCookie(this.setting.formid+"_persist", i); //enable persistence?
            this.currentsection=i;
        }
    },

    addvalidatefields:function(){
        var $=jQuery, setting=this.setting, theform=this.$theform.get(0), validatefields=[];
        validatefields=setting.validate; //array of form element ids to validate
        for (var i=0; i<validatefields.length; i++){
            var el=theform.elements[validatefields[i]]; //reference form element
            if (el){ //if element is defined
                var $section=$(el).parents('fieldset.sectionwrap:eq(0)'); //find fieldset.sectionwrap this form element belongs to
                if ($section.length==1){ //if element is within a fieldset.sectionwrap element
                    $section.data('elements').push(el); //cache this element inside corresponding section
                }
            }
        }
    },

    validate:function(section){
        var elements=this.sections.$sections.eq(section).data('elements'); //reference elements within this section that should be validated
        var validated=true, invalidtext=["Please fill out the following fields:\n"];
        function invalidate(el){
            validated=false;
            invalidtext.push("- "+ (el.id || el.name));
        }
        for (var i=0; i<elements.length; i++){
            if (/(text)/.test(elements[i].type) && elements[i].value===""){ //text and textarea elements
                invalidate(elements[i]);
            }
            else if (/(select)/.test(elements[i].type) && (elements[i].selectedIndex==-1 || elements[i].options[elements[i].selectedIndex].text==="")){ //select elements
                invalidate(elements[i]);
            }
            else if (elements[i].type===undefined && elements[i].length>0){ //radio and checkbox elements
                var onechecked=false;
                for (var r=0; r<elements[i].length; r++){
                    if (elements[i][r].checked===true){
                        onechecked=true;
                        break;
                    }
                }
                if (!onechecked){
                    invalidate(elements[i][0]);
                }
            }
        }
        if (!validated)
            alert(invalidtext.join('\n'));
        return validated;
    },


    init:function(setting){
        var thiswizard=this;
        jQuery(function($){ //on document.ready
            var $theform=$('#'+setting.formid);
            if ($theform.length===0) $theform=$('form[name='+setting.formid+']'); //if form with specified ID doesn't exist, try name attribute instead
            if (setting.manualfieldsets && setting.manualfieldsets.length>0) thiswizard.createfieldsets($theform, setting.manualfieldsets);
            var $stepsguide=$('<div class="stepsguide" />'); //create Steps Container to house the "steps" text
            var $sections=$theform.find('fieldset.sectionwrap').hide(); //find all fieldset elements within form and hide them initially
            if (setting.revealfx[0]=="slide"){ //create outer DIV that will house all the fieldset.sectionwrap elements
                $sectionswrapper=$('<div style="position:relative;overflow:hidden;"></div>').insertBefore($sections.eq(0)); //add DIV above the first fieldset.sectionwrap element
                $sectionswrapper_inner=$('<div style="position:absolute;left:0;top:0;"></div>'); //create inner DIV of $sectionswrapper that will scroll to reveal a fieldset element
            }
            var maxfieldsetwidth=$sections.eq(0).outerWidth(); //variable to get width of widest fieldset.sectionwrap
            $sections.slice(1).each(function(i){ //loop through $sections (starting from 2nd one)
                maxfieldsetwidth=Math.max($(this).outerWidth(), maxfieldsetwidth);
            });
            maxfieldsetwidth+=2; //add 2px to final width to reveal fieldset border (if not removed via CSS)
            thiswizard.maxfieldsetwidth=maxfieldsetwidth;
            $sections.each(function(i){ //loop through $sections again
                var $section=$(this);
                if (setting.revealfx[0]=="slide"){
                    $section.data('page', i).css({position:'absolute', top:0, left:maxfieldsetwidth*i}).appendTo($sectionswrapper_inner); //set fieldset position to "absolute" and move it to inside sectionswrapper_inner DIV
                }
                $section.data('elements', []); //empty array to contain elements within this section that should be validated for data (applicable only if validate option is defined)
                //create each "step" DIV and add it to main Steps Container:
                var $thestep=$('<div class="step disabledstep" />').data('section', i).html('Step '+(i+1)+'<div class="smalltext">'+$section.find('legend:eq(0)').text()+'<p></p></div>').appendTo($stepsguide);
                $thestep.click(function(){ //assign behavior to each step div
                    thiswizard.loadsection($(this).data('section'));
                });
            });
            if (setting.revealfx[0]=="slide"){
                $sectionswrapper.width(maxfieldsetwidth); //set fieldset wrapper to width of widest fieldset
                $sectionswrapper.append($sectionswrapper_inner); //add $sectionswrapper_inner as a child of $sectionswrapper
            }
            $theform.prepend($stepsguide); //add $thesteps div to the beginning of the form
            //$stepsguide.insertBefore($sectionswrapper) //add Steps Container before sectionswrapper container
            var $thesteps=$stepsguide.find('div.step');
            //create pagination DIV and add it to end of form:
            var $paginatediv=$('<div class="formpaginate" style="overflow:hidden;"><span class="prev" style="float:left">Prev</span> <span class="status">Step 1 of </span> <span class="next" style="float:right">Next</span></div>');
            $theform.append($paginatediv);
            thiswizard.$theform=$theform;
            if (setting.revealfx[0]=="slide"){
                thiswizard.sections={$outerwrapper:$sectionswrapper, $innerwrapper:$sectionswrapper_inner, $sections:$sections, count:$sections.length}; //remember various parts of section container
                thiswizard.sections.$sections.show();
            } else{
                thiswizard.sections={$sections:$sections, count:$sections.length}; //remember various parts of section container
            }
            thiswizard.$thesteps=$thesteps; //remember this ref
            thiswizard.paginatediv={$main:$paginatediv, $navlinks:$paginatediv.find('span.prev, span.next'), $status:$paginatediv.find('span.status')}; //remember various parts of pagination DIV
            thiswizard.paginatediv.$main.click(function(e){ //assign behavior to pagination buttons
                if (/(prev)|(next)/.test(e.target.className)) thiswizard.loadsection(e.target.className);
            });
            var i=(setting.persistsection)? formtowizard.routines.getCookie(setting.formid+"_persist") : 0;
            thiswizard.loadsection(i||0, true); //show the first section
            thiswizard.setting.oninit($, i, $sections.eq(i)); //call oninit event handler
            if (setting.validate){ //if validate array defined
                thiswizard.addvalidatefields(); //seek out and cache form elements that should be validated
                thiswizard.$theform.submit(function(){
                    var returnval=true;
                    for (var i=0; i<thiswizard.sections.count; i++){
                        if (!thiswizard.validate(i)){
                            thiswizard.loadsection(i, true);
                            returnval=false;
                            break;
                        }
                    }
                    return returnval; //allow or disallow form submission
                });
            }
        });
    }
};

formtowizard.routines={

    getCookie:function(Name){
        var re=new RegExp(Name+"=[^;]+", "i"); //construct RE to search for target name/value pair
        if (document.cookie.match(re)) return document.cookie.match(re)[0].split("=")[1]; //if cookie found return it's value
        return null;
    },

    setCookie:function(name, value){
        document.cookie = name+"=" + value + ";path=/";
    }
};
